package com.itheima.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 封装增强代码方法的类
 *  通知类|切面类
 */
//@Component//前提：切面类要被IOC容器管理
//@Aspect//定义当前类为切面类
public class MyAdvice {


    /**
     *   我们发现切入点表达式存在大量冗余
     *         所以，我们可以将表达式提取出来，封装使用
     *    说明；
     *       @Pointcut切入点表达式注解作用的方法必须无返回值，无入参，无实现体
     *       换言之，方法仅仅是注解的一个载体
     */
    @Pointcut("execution(public * com.itheima.service.impl.*.insert(..))")
    public void myPt(){}


    /**
     * 前置通知方法 advice
     * 表达式扫中的方法都是切入点
     * 当前方法定义了切入点+通知=切面
     * 场景：
     *  满足目标方法执行前，要做增强的功能
     *      类似于过滤机制
     */
//    @Before("execution(public * com.itheima.service.impl.*.insert(..))")
//    @Before("myPt()")
//    public void before(){
//        System.out.println("前置增强运行了.....");
//    }

    /**
     * 后置增强方法
     * 特点：方法正常运行之后，才被执行，
     * 如果目标方法执行出现异常，不能被运行
     * 场景：
     *   满足类方法顺利执行成功后，要做什么事
     *   复检，
     *   插入订单数据后，触发通知的操作；
     */
//    @AfterReturning("execution(public * com.itheima.service.impl.*.insert(..))")
//    @AfterReturning("myPt()")
//    public void afterReturning(){
//        System.out.println("后置通知运行了.....");
//    }

    /**
     * 异常通知方法
     *  只有出现异常，才做增强的通知
     *  场景：
     *      出现异常，通知
     *      类似：程序出现异常，通知运维人员（发送邮箱 短信等）
     *      警告
     *      触发的实际：原始方法出现异常后，必须向外抛出
     */
//    @AfterThrowing("myPt()")
//    public void afterThrowing(){
//        System.out.println("异常通知运行了......");
//    }

    /**
     * 最终通知方法
     *  特点：无论是否发生异常，都会被执行
     *  场景：
     *      日志（无论成功或者失败，都记录日志） 释放资源
     *      做收尾工作
     *
     */
//    @After("myPt()")
//    public void after(){
//        System.out.println("最终通知运行了......");
//    }

    /**
     * 环绕通知 重点
     * @param pjp  表示切入点 (底层是对当前代理对象调用的方法的封装)
     *             spring底层会将当前增强的方法封装到ProceedingJoinPoint类型对象下，并注入环绕通知方法
     *             参数下
     * @return 方法的返回值
     * 环绕通知，一般独立使用，不会配置前置后置等通知使用
     *  当然也可配合使用！
     *  注意点：
     *   要想实现环绕通知，必须先获取目标方法
     */
    @Around("myPt()")
    public Object around(ProceedingJoinPoint pjp){
        System.out.println("前置通知运行了。。。。");
        Object result = null;
        try {
            //执行目标方法，执行结果必须返回，否则会被环绕通知方法吃掉，导致外界无法获取方法的返回值
            //result= method.invoke(bean,args)；
            Object target = pjp.getTarget();//获取目标对象，被代理对象
            Object aThis = pjp.getThis();//当前对象，生成的代理对象
            Object[] args = pjp.getArgs();//目标方法的入参
            String name = pjp.getSignature().getName();//目标方法的名称
            result = pjp.proceed();
            System.out.println("后置通知运行了。。。。");
        } catch (Throwable throwable) {
            System.out.println(throwable.getCause()+"异常通知运行");
        } finally {
            System.out.println("最终通知运行了......");
        }
        return result;
    }
}
